package com.salt.security.config;

import com.salt.security.UserDetailsServiceImpl;
import com.salt.security.filter.JwtRequestFilter;
import com.salt.security.wechat.auth.JsCodeAuthenticationProvider;
import com.salt.security.wechat.auth.WeChatPasswordAuthenticationProvider;
import com.salt.system.enums.SysAuthorities;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.filter.CorsFilter;

/**
 * SpringSecurity配置
 *
 * @author HaiBo Kuang
 * @date 2021年04月16日 下午 12:29
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 管理员
     */
    final String[] adminUrls = {"/admin/**"};
    /**
     * 允许直接访问
     */
    final String[] permitUrls = {"/auth/**", "/demo/**"};

    private final UserDetailsService userDetailsService;
    private final JwtRequestFilter jwtRequestFilter;
    private final JsCodeAuthenticationProvider jsCodeAuthenticationProvider;
    private final WeChatPasswordAuthenticationProvider weChatPasswordAuthenticationProvider;


    @Autowired
    public SecurityConfig(UserDetailsServiceImpl impl,
                          JwtRequestFilter jwtRequestFilter,
                          JsCodeAuthenticationProvider jsCodeAuthenticationProvider,
                          WeChatPasswordAuthenticationProvider weChatPasswordAuthenticationProvider) {
        this.userDetailsService = impl;
        this.jwtRequestFilter = jwtRequestFilter;
        this.jsCodeAuthenticationProvider = jsCodeAuthenticationProvider;
        this.weChatPasswordAuthenticationProvider = weChatPasswordAuthenticationProvider;
    }

    /**
     * 配置认证方式
     * <p>
     * 用户名（手机号登陆）配置使用UserDetailsService接口认证，用户需实现此接口见{@link UserDetailsServiceImpl}
     * 具体见{@link AbstractUserDetailsAuthenticationProvider}中{authenticate}方法
     * <p>
     * jsCode登陆验证器 {@link JsCodeAuthenticationProvider}
     *
     * @param auth 身份验证管理器生成器
     * @throws Exception 异常
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
        // 自定义用户微信公众号or小程序密码登陆JsCode登录逻辑
        auth.authenticationProvider(jsCodeAuthenticationProvider);
        // 自定义用户微信公众号or小程序密码登陆，（这里有用户openId绑定行为）
        auth.authenticationProvider(weChatPasswordAuthenticationProvider);
    }

    /**
     * 配置http请求安全配置
     *
     * @param http HttpSecurity
     * @throws Exception 异常信息
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()
                .csrf().disable()
                .authorizeRequests()
                .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
                // 自定义用户权限时：
                // 在用户UserDetails中, Collection<? extends GrantedAuthority> 用户权限authorities：应为[ROLE_ADMIN]
                // 具体请看 ExpressionUrlAuthorizationConfigurer.hasRole 或 hasAnyRole方法
                // 简单来说，SpringSecurity会自动拼接，"ROLE_"
                // 当然有不自动拼接的验证方法， 如 hasAuthority 由开发者自行选择
//                .antMatchers(adminUrls).hasRole("ADMIN")
                .antMatchers(adminUrls).hasAuthority(SysAuthorities.ADMIN.getAuthority())
                .and()
                .authorizeRequests()
                .antMatchers(permitUrls).permitAll()
                .anyRequest().authenticated()
                .and()
                .sessionManagement()
                // 设置session 无状态
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
        http.addFilterBefore(jwtRequestFilter, CorsFilter.class);
    }

    /**
     * 配置密码编码器
     * <P>注册，修改密码时；使用此编码器对密码进行加密</P>
     * 具体请看 PasswordEncoder
     *
     * @return 密码编码器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 解决 无法直接注入 AuthenticationManager
     *
     * @return AuthenticationManager
     * @throws Exception 异常
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}
